/* Papastefanos Serafeim */
/* Sunarthseis gia epikoinwnia 
 * Sto arxeio auto uparxoun non-ansi shmeia kai einai to monadiko
 * sto opoio uparxoun diafores metaju ths ekdoshs gia linux kai gia
 * windows. Oles oi sunarthseis epistrefoun 0 an sumvei kapoio la9os. */

#include "low.h"
#include "helpers.h"

void mySocketClose(SOCKET s) {
	shutdown(s,2);
	#ifdef _CWMP_WIN
	closesocket(s);
	#else
	close(s);
	#endif
}

/* Spazei ena HTTP mhnuma pou periexei 1 h perissotera SOAP mhnumata sta
 * epimerous SOAP mhnumata. */
StringList *splitRawMessage(char *rawMessage) {
	unsigned int i;
	unsigned int num = getNumOfMessages(rawMessage);
	if(num==0) return NULL;
	StringList *messages = newStringList(num);
	
	for(i=0;i<num;i++) {
		int l = strstr(rawMessage, "</soap:Envelope>") - rawMessage + strlen("</soap:Envelope>");
		messages->strings[i]=(char *)malloc( (l+1)*sizeof(char) );
		strncpy(messages->strings[i],rawMessage,l);
		messages->strings[i][l]=0;
		rawMessage = strstr(rawMessage, "</soap:Envelope>") + strlen("</soap:Envelope>");
	}
	return messages;
}

/* Epistrefei ton ari9mo twn epimerous SOAP mhnumatwn pou stal9hkan me
 * ena HTTP response apo ton server. */
unsigned int getNumOfMessages(char *rawMessage) {
	int i=0;
	if(rawMessage == NULL) return 0;
	while(strstr(rawMessage, "<soap:Envelope") ) {
		rawMessage = strstr(rawMessage, "</soap:Envelope>" );
		rawMessage = rawMessage+strlen("</soap:Envelope>");
		i++;
	}
	return i;
}


/* Ejetazei an lhf9hke to swsto connection string. Ws proth
 * ekdoxh aplws elegxei an sto mhnuma tou server uparxei to
 * mystring. */
int checkConnectionString(char *mystring, SOCKET sd) {
	char *buf = getLine(sd);
	if(strstr(buf, mystring) ) return 1;
	else return 0;
}


/* Dhmiourgei tous Headers enos HTTP mhnumatos pou 9a stallei san ena
 * StringList. Xreiazetai ws parametro to mege9os tou mhnumatos, gia
 * to Content-Length header. */
StringList *createHeaders(unsigned int msglen) {
	StringList *headers = newStringList(5);

	headers->strings[0]=strdup("POST * HTTP/1.1\r\n");
	headers->strings[1]=strdup("User-Agent: CWPM Client by Papastefanos Serafeim\r\n");
	headers->strings[2]=(char *)malloc(80*sizeof(char) );
	sprintf(headers->strings[2],"Content-Length: %d\r\n", msglen );
	
	headers->strings[3]=strdup("SOAPAction:\r\n");
	headers->strings[4]=strdup("\r\n");
	return headers;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////


#ifdef _CWMP_WIN // Windows 
/* H sunarthsh auth arxikopoiei ta windows sockets. Xreiazetai mono
   sthn windows ekdosh tou client. */
void initWinsock(void) {
	WSADATA wsaData;
	WORD version;
	int error;

	version=MAKEWORD(2,0);
	error=WSAStartup(version, &wsaData);
	if(error!=0) {
		PRINTD("Error while initializing winsock2.");
		exit(-1);
	}
	
	if( (LOBYTE(wsaData.wVersion)!=2)||(HIBYTE(wsaData.wVersion)!=0) ) {
		WSACleanup();
		PRINTD("Error while initializing winsock2, wrong version.");
		exit(-1);
	}	

}


/* Me th sunarthsh auth o client sundeetai sth d/sh address kai th 9ura port */
SOCKET makeConnection(char *address, int port) {
	SOCKET sd; // connecting socket 
	struct sockaddr_in server; // connecting address 

	if ( ( sd=socket(AF_INET, SOCK_STREAM, 0) ) == INVALID_SOCKET ) {
		perror("cannot create socket");
		PRINTD("%d", WSAGetLastError() );
		return 0;
	}
	
	/* set up sockaddr_in */
	server.sin_family=AF_INET;
	server.sin_port=htons(port);
	server.sin_addr.s_addr=inet_addr(address);
	memset(&(server.sin_zero),'\0',8);
	PRINTD("Sending request...\n");
	/* Connect */
	if (connect(sd, (struct sockaddr *)&server, sizeof(server))== INVALID_SOCKET) {
		PRINTD("cannot connect to server");
		PRINTD("%d", WSAGetLastError() );
		return 0;
	}
	return sd;
}

/* Dhmiourgei ena socket to opoio akouei sth 9ura port */
SOCKET createListeningSocket(int port) {
	SOCKET lsd;
	struct sockaddr_in sin;
	int slen=sizeof(struct sockaddr_in);
	/* Create a listening socket */
	if ( ( lsd=socket(AF_INET, SOCK_STREAM, 0) ) == (int)INVALID_SOCKET ) {
		perror("cannot create listening socket");
		return 0;
	}

	/* Bind listening socket to port */
	sin.sin_family=AF_INET;
	sin.sin_port=htons(port);
	sin.sin_addr.s_addr=htonl(INADDR_ANY); /* IP d/sh tou susthmatos mas 	*/
	if (bind(lsd, (struct sockaddr *)&sin, slen ) == (int)INVALID_SOCKET  ) {
		perror("cannot bind listening socket");
		return 0;
	}
	
	/* Initiate a listen queue */
	if (listen(lsd, 1) == (int)INVALID_SOCKET  ) {
		perror("cannot listen o socket");
		return 0;
	}
	return lsd;
}

/* Kanei accept mia sundesh */
SOCKET acceptConnection(SOCKET lsd) {
	SOCKET sd;
	int slen=sizeof(struct sockaddr_in);
	struct sockaddr_in in_sa; /* remote address */

	if( (sd=accept(lsd, (struct sockaddr *)&in_sa, &slen)) == INVALID_SOCKET ) {
		perror("Cannot accept the connection");
		return 0;
	}
	PRINTD("Got a connection.\n");
	return sd;
}

/* Epistrefei thn epomenh grammh apo to socket. Xreiazetai mono kata
 * thn anagnwsh ths epikefalidas tou HTTP mhnumatos. Epistrefetai enas
 * NULL pointer se periptwsh sfalmatos. H sunarthsh auth ousiastika 
 * diavazei sunexws enan enan tous xarakthres apo to socket mexri na
 * diavasei ton CR (\r). Sth sunexeia, ejetazei an o epomenos xarakthras 
 * einai o LF (\n) opote jeroume oti h grammh exei teleiwsei, alliws
 * sunexizetai h anagnwsh xarakthrwn. */
char *getLine(SOCKET sd) {
	char c;
	char buf[MAXBUF]; 
	int i=0;
	while(1) {
		if( recv(sd, &c, 1, 0) == SOCKET_ERROR ) {
			PRINTD("%d.", WSAGetLastError() );
			perror("recv, CANNOT READ 1 char");
			return NULL;
		};

		buf[i++]=c;
		if(i==MAXBUF-1) {
			PRINTD("HTTP header line too long");
			return NULL;
		}

		if(c=='\r') { // GOT CR
			recv(sd, &c, 1, 0); 
			if(c == '\n') { // GOT LF
				buf[i++]=c;
				break ; 
			} 
		}
	}
	// Gia na ftasoume edw exei lhf9ei oloklhrh grammh
	char *tmp = (char *)malloc((i+1)*sizeof(char) );
	strncpy(tmp, buf, i);
	tmp[i]='\0';
	return tmp;
}

/* H sunarthsh auth diavazei ena HTTP mhnuma apo to socket. Arxika,
 * diavazei grammh-grammh thn epikefalida mexri na vrei th grammh
 * Content-Length: h opoia kai dinei to mege9os tou HTTP mhnumatos
 * to opoio kai apo9hkeuei. Sth sunexeia, sunexizei na diavazei grammes
 * mexri na vrei mia kenh grammh. Apo ekei kai pera, sumfwna me to HTTP
 * prwtokollo, 9a uparxoun tosoi xarakthres osoi kai to mege9s tou
 * mhnumatos pou diavasthke pio prin, opote kai diavazontai oloi, me
 * th xrhsh epimonou tropou wste na einai sigouro oti de 9a lhf9ei miso
 * mhnuma. */
char *getRawMessage(SOCKET sd) {
	int len;
	char *tmp;
	while(1) {
		tmp=getLine(sd);
		//puts(tmp);
		if(tmp == NULL) return NULL;
		if(strstr(tmp, "Content-Length:") ) {
			
			len=atoi(&tmp[15]);
			//if(len==0) return NULL; 
		}

		if(tmp[0]=='\r') { //Adeia grammh
			free(tmp);	
			break;
		}
		free(tmp);
	}

	if(len==0) return NULL; 
	char *raw=(char *)malloc((len+1)*sizeof(char ) ) ;
	int totalbytes=0;
	int bytes=0;
	while(totalbytes<len) {
		bytes=recv(sd, raw+totalbytes, len-totalbytes, 0);
		totalbytes+=bytes;
	}
	raw[len]=0;
	
	return raw;
}

/* Epistrefei ta SOAP mhnumata pou uparxoun sto socket xrhsimopoiwntas
 * tis getRawMessage() kai splitRawMessage */
StringList *getMessages(SOCKET sd) {
	StringList *slist;
	char *buf = getRawMessage(sd);
	if(buf == NULL) {
		slist = newStringList(0);
		return slist;
	}
	slist = splitRawMessage(buf);
	free(buf); 
	return slist;
}

/* Stellei ta mhnumata messages ws HTTP mhnuma. Afou parei ta headers apo thn
 * createHeaders, antigrafei headers kai mhnumata se ena string katallhlou 
 * mege9ous, to opoio kai stelletai sth sunexeia mesw tou socket. */
void sendRawMessage(SOCKET sd, StringList *messages) {
	unsigned int msglen=0, totalen=0, pos=0, i;
	for(i=0;i<messages->size;i++) msglen+=strlen(messages->strings[i]);
	StringList *headers = createHeaders(msglen);
	
	for(i=0;i<headers->size;i++) totalen+=strlen(headers->strings[i]);
	totalen+=msglen+1;

	char *raw = (char *)malloc(totalen*sizeof(char) ) ;
	
	for(i=0;i<headers->size;i++) {
		strncpy(raw+pos, headers->strings[i], strlen(headers->strings[i]) );
		pos+=strlen(headers->strings[i]);
	}

	for(i=0;i<messages->size;i++) {
		strncpy(raw+pos, messages->strings[i], strlen(messages->strings[i]) );
		pos+=strlen(messages->strings[i]);
	}

	raw[totalen-1]=0;
	
	send(sd, raw, strlen(raw), 0);

	freeStringList(headers);
	free(raw);
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////


#else  // Unix

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "low.h"
#include "helpers.h"

SOCKET makeConnection(char *address, int port) {
	SOCKET sd; /* connecting socket */
	struct sockaddr_in server; /* connecting address */

	if ( ( sd=socket(AF_INET, SOCK_STREAM, 0) ) < 0 ) {
		perror("cannot create socket");
		exit(-1);
	}
	
	/* set up sockaddr_in */
	server.sin_family=AF_INET;
	server.sin_port=htons(port);
	server.sin_addr.s_addr=inet_addr(address);
	memset(&(server.sin_zero),'\0',8);
	PRINTD("Sending request...\n");
	/* Connect */
	if (connect(sd, (struct sockaddr *)&server, sizeof(server))<0) {
		PRINTD("cannot connect to server");
		exit(-1);
	}
	return sd;
}

SOCKET createListeningSocket(int port) {
	SOCKET lsd;
	struct sockaddr_in sin;
	int slen=sizeof(struct sockaddr_in);
	/* Create a listening socket */
	if ( ( lsd=socket(AF_INET, SOCK_STREAM, 0) ) < 0 ) {
		perror("cannot create listening socket");
		exit(-1);
	}

	/* Bind listening socket to port */
	sin.sin_family=AF_INET;
	sin.sin_port=htons(port);
	sin.sin_addr.s_addr=htonl(INADDR_ANY); /* IP d/sh tou susthmatos mas 	*/
	if (bind(lsd, (struct sockaddr *)&sin, slen ) < 0  ) {
		perror("cannot bind listening socket");
		exit(-1);
	}
	
	/* Initiate a listen queue */
	if (listen(lsd, 1) < 0  ) {
		perror("cannot listen o socket");
		exit(-1);
	}
	return lsd;
}


SOCKET acceptConnection(SOCKET lsd) {
	SOCKET sd;
	int slen=sizeof(struct sockaddr_in);
	struct sockaddr_in in_sa; /* remote address */

	if( (sd=accept(lsd, (struct sockaddr *)&in_sa,(socklen_t*) &slen)) < 0 ) {
		perror("Cannot accept a connection");
		exit(-1);
	}
	return sd;
}

char *getLine(SOCKET sd) {
	char c;
	char buf[256];
	int i=0;
	while(1) {
		
		if( recv(sd, &c, 1, 0) < 0 ) {
			perror("recv, CANNOT READ 1 char");
			exit(-1);
		};

		buf[i++]=c;


		if(c=='\r') { // GOT CR
			recv(sd, &c, 1, 0); // GOT LF
			buf[i++]=c;
			break ; 
		}
	}
	// Gia na ftasoume edw exei lhf9ei oloklhrh grammh
	char *tmp = (char *)malloc((i+1)*sizeof(char) );
	strncpy(tmp, buf, i);
	tmp[i]='\0';
	return tmp;
}

char *getRawMessage(SOCKET sd) {
	int len;
	char *tmp;
	while(1) {
		tmp=getLine(sd);
		//puts(tmp);
		
		if(strstr(tmp, "Content-Length:") ) {
			
			len=atoi(&tmp[15]);
//			if(len==0) return NULL; // PROSOXH EDW ISWS UPARXEI PROVLHMA
		}

		if(tmp[0]==13) { //Adeia grammh
			free(tmp);	
			break;
		}
		free(tmp);
	}

	if(len==0) return NULL; // ALLAGH 
	char *raw=(char *)malloc((len+1)*sizeof(char ) ) ;
	int totalbytes=0;
	int bytes=0;
	while(totalbytes<len) {
		bytes=recv(sd, raw+totalbytes, len-totalbytes, 0);
		totalbytes+=bytes;
	}
	raw[len]=0;
	
	return raw;
}

StringList *getMessages(SOCKET sd) {
	StringList *slist;
	char *buf = getRawMessage(sd);
	if(buf == NULL) {
		slist = newStringList(0);
		return slist;
	}
	slist = splitRawMessage(buf);
	free(buf); // Teleutaia allagh !!
	return slist;
}

void sendRawMessage(SOCKET sd, StringList *messages) {
	unsigned int msglen=0, totalen=0, pos=0, i;
	for(i=0;i<messages->size;i++) msglen+=strlen(messages->strings[i]);
	StringList *headers = createHeaders(msglen);
	
	for(i=0;i<headers->size;i++) totalen+=strlen(headers->strings[i]);
	totalen+=msglen+1;

	char *raw = (char *)malloc(totalen*sizeof(char) ) ;
	
	for(i=0;i<headers->size;i++) {
		strncpy(raw+pos, headers->strings[i], strlen(headers->strings[i]) );
		pos+=strlen(headers->strings[i]);
	}

	for(i=0;i<messages->size;i++) {
		strncpy(raw+pos, messages->strings[i], strlen(messages->strings[i]) );
		pos+=strlen(messages->strings[i]);
	}

	raw[totalen-1]=0;
	
	send(sd, raw, strlen(raw), 0);

	freeStringList(headers);
	free(raw);
}


#endif
